﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using CoreEx.Common.Extensions;
using CoreEx.Common.Validation;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Common.Implementation.Validation
{
    /// <summary>
    /// Represents a set of validation rules.
    /// </summary>
    /// <typeparam name="T">The target type that the validation rules applies to.</typeparam>
    public class ValidationRules<T> : IValidationRules<T>, IInitialize
    {

        /// <summary>
        /// A list of validation rules that applies to specific properties
        /// </summary>
        private readonly IDictionary<string, IList<IValidationRule<T>>> _propertyRules
            = new Dictionary<string, IList<IValidationRule<T>>>();

        /// <summary>
        /// A list of validation rules that applies to target instance.
        /// </summary>
        private readonly IList<IValidationRule<T>> _validationRules = new List<IValidationRule<T>>();

                
        /// <summary>
        /// Adds a new validation rule.
        /// </summary>
        /// <param name="rule">The <see cref="Expression{TDelegate}"/> that contains the validating code.</param>
        /// <param name="message">The message to be used when a validation rule is broken.</param>
        public virtual void AddRule(Expression<Func<T, bool>> rule, string message)
        {            
            var targetProperties = ResolveTargetPropertiesFromExpression(rule);

            var actualRule = RuleRewriter.Rewrite(rule,targetProperties);

            var validationRule = CreateValidationRule(actualRule, message);                        
                     
            targetProperties.ForEach(
                p => AddPropertyValidationRule(p.Name,validationRule));            
            _validationRules.Add(validationRule);
        }

        /// <summary>
        /// Gets all the <see cref="IValidationRule{T}"/> instances for the target type.
        /// </summary>
        /// <returns>An <see cref="IEnumerable{T}"/> that contains <see cref="IValidationRule{T}"/> instances.</returns>
        public virtual IEnumerable<IValidationRule<T>> GetRules()
        {
            return _validationRules;
        }

        /// <summary>
        /// Gets all the <see cref="IValidationRule{T}"/> instances that applies to the target type and <paramref name="propertyName"/>.
        /// </summary>
        /// <param name="propertyName">The name of the property for which to retrieve the validation rules.</param>
        /// <returns>An <see cref="IEnumerable{T}"/> that contains <see cref="IValidationRule{T}"/> instances.</returns>
        public virtual IEnumerable<IValidationRule<T>> GetRules(string propertyName)
        {
            if (_propertyRules.ContainsKey(propertyName))
                return _propertyRules[propertyName];

            return null;
        }


        private void AddPropertyValidationRule(string propertyName, IValidationRule<T> validationRule)
        {
            if (!_propertyRules.ContainsKey(propertyName))
                _propertyRules.Add(propertyName,new List<IValidationRule<T>>());
            _propertyRules[propertyName].Add(validationRule);
        }



        /// <summary>
        /// Resolves the target properties from the validation expression.
        /// </summary>
        /// <param name="expression">The <see cref="Expression{TDelegate}"/> that describes the validation functor.</param>
        /// <returns>An <see cref="IEnumerable{T}"/> that contains the target properties.</returns>
        private IEnumerable<PropertyInfo> ResolveTargetPropertiesFromExpression(Expression<Func<T, bool>> expression)
        {
            return TargetPropertyResolver.ResolveFrom(expression);            
        }

        /// <summary>
        /// Creates a new <see cref="IValidationRule{T}"/> instance.
        /// </summary>
        /// <param name="rule">The <see cref="Expression{TDelegate}"/> that describes the validation functor.</param>
        /// <param name="message">The message to be used when the validation rule is broken.</param>
        /// <returns>An <see cref="IValidationRule{T}"/> instance.</returns>
        private static IValidationRule<T> CreateValidationRule(Expression<Func<T, bool>> rule, string message)
        {
            var compiledRule = rule.Compile();            
            return new ValidationRule<T> {Message = message, Rule = compiledRule};
        }

        /// <summary>
        /// Gets or sets the <see cref="ITargetPropertyResolver"/> instance used to determine target properties.
        /// </summary>
        public ITargetPropertyResolver TargetPropertyResolver { get; set; }



        public IRuleRewriter RuleRewriter { get; set; }

        /// <summary>
        /// Initializes this service instance.
        /// </summary>
        /// <param name="source">The <see cref="IServiceContainer"/> that created this service instance.</param>
        public virtual void Initialize(IServiceContainer source)
        {
            TargetPropertyResolver = source.GetService<ITargetPropertyResolver>();
            RuleRewriter = source.GetService<IRuleRewriter>();
            source.GetServices<IRuleInjector<T>>().ForEach(i => i.Inject(this));
        }
    }
}
